package engine;

import gameobject.Exit;
import gameobject.Object;
import gameobject.Room;
import gameobject.User;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.JFrame;

import tree.Node;
import tree.Tree;



/**
 * This is the main class. The GUI, map, items, player etc. are initialized here.
 * It also contains engine related stuff, which I should move into a separate class.
 * 
 * The main loop checks for user input and processes it in the process() function.
 * This will soon be changed for the GUI stuff. The main loop should watch for
 * mouse clicks and act accordingly.
 * 
 * @author Simon Dehne
 *
 */
public class JLeibniz {
	
	static public Tree map;

	 // For writing logs
	public static BufferedWriter outWriter;
	
	// for reading files
	static FileReader input;
	static BufferedReader bufRead;	
	
	// Lists of all rooms and items
	static Map<String, Room> roomList = new HashMap<String, Room>();
	static Map<String, Object> itemList = new HashMap<String, Object>();
	
	// How long a period (a frame) should take; here 10ms = 100FPS
	static long period = 10;	

	/*
	 * The player character.
	 */
	static private User player;
	
	public User getUser() {
		return player;
	}
	
	/*
	 * Output is used to draw messages on the screen. Any message
	 * that needs to be drawn is saved in this String. The mainFrame,
	 * which holds the GUI, reads this String and displays it in the
	 * paint() method.
	 */
	static public String output="";
	
	/*
	 * Holds the GUI.
	 */
	public static JFrame mainFrame;
	
	// Debug mode on or off
	public static boolean isDebug = false;
	
	/**
	 * Initializes everything necessary. Opens the GUI first, then creates the
	 * commandMap, the worldmap, the items and the player. Finally, display a
	 * welcome message.
	 * @throws IOException 
	 */
	private static void init() throws IOException {

		// initialize the writer for log files if debug mode is on
		if(isDebug) outWriter = new BufferedWriter(new FileWriter("logs/log"+System.currentTimeMillis()+".txt", true));
			
		// open the GUI
		mainFrame = new MainFrame();
		
		createItems();
		createMap();
		createPlayer();				
		
		output("Welcome to JLeibniz. You are inside the Leibniz University" +
				" of Hanover.");	
		
		/*
		 * *************
		 * TESTING
		 * *************
		 */
		map = new Tree();
		
		// create a 80x60 walkmap (1/10 of the screen resolution) 
		for(int i=0; i<=80; i++) {
			for(int j=0; j<=60; j++) {
				map.addNode(new Node(i, j, true));
				System.out.println("Adding "+i+","+j);
			}		
		}
		
		// walkable area is between 61, 125 to 200, 150
	/*	for(int i=61; i<200; i++) {
			for(int j=125; j<150; j++) {
				map.getNode(i, j).toggleWalkable();
				System.out.println("Toggling "+i+","+j);
			}
		}*/

		// Non walkable area (just a vertical line)
		for(int i=40; i<59; i++) {
			map.getNode(49, i).toggleWalkable();

		}

		// set an area as walkable
		/*for(int i=0; i<=8; i++) {
			for(int j=3; j<=6; j++) {
				map.getNode(i, j).toggleWalkable();
			}			
		}
		
		map.getNode(1, 3).toggleWalkable();
		map.getNode(1, 4).toggleWalkable();*/

		
		//AStar test = new AStar(map, map.getNode(0, 3), map.getNode(2, 3));
		
		/* *************************
		 * HOLY SHIT PATHFINDING
		 * *************************
		 */
		
	}
	
	/**
	 * Create items that the player can use and/or combine. Items need a name,
	 * a description and their position on the screen.
	 * @throws IOException 
	 *  
	 */
	private static void createItems() throws IOException {
		
		loadFile("res/objects/items", "items");

		if(JLeibniz.isDebug) outWriter.write("Items created.\n");
	}
	
	/**
	 * Creates the world map. Reads in rooms and descriptions for them from
	 * an external file.
	 * @throws IOException 
	 */
	private static void createMap() throws IOException {
	
		loadFile("res/rooms/rooms", "rooms");		
		if(JLeibniz.isDebug) outWriter.write("Map created.\n");	
	}
	
	/**
	 * Creates the player character and places them in the starting position.
	 * @throws IOException 
	 */
	private static void createPlayer() throws IOException {		
		// Create the player
		player = new User();
		player.setCurrentRoom(roomList.get("developmentHell"));
		
		if(JLeibniz.isDebug) outWriter.write("Player created.\n");
	}
	
	/**
	 * Loads a text file and reads rooms or items from it.
	 * 
	 * @param filepath	Filepath to the file.
	 * @param type		Type of objects that should be created.
	 */
	private static void loadFile(String filepath, String type) {
		
		try {
			// Read stuff from the specified file
			input = new FileReader(filepath);
			bufRead = new BufferedReader(input);
			
			// Currently read line
			String line = bufRead.readLine();
			
			// While there are lines to read
			while((line != null)) {

				// Skip lines that are comments or empty
				// TODO: Skip lines that contain whitespace, such as spaces or tabs
				while(line.length() == 0 || line.startsWith("#")) {
					line = bufRead.readLine();
				}
					
				// according to type, either create rooms or items
				if(type == "rooms") {
					readRooms(line);
				}
				else if(type == "items") {
					readItems(line);
				}
				else {
					System.err.println("Read error.");
				}

				line = bufRead.readLine();
			}
			
			input.close();
			bufRead.close();
		}
		catch (FileNotFoundException e) {
			System.err.println("File not found.");
		} catch (IOException e) {
			System.err.println("IO Exception.");
		}
	}

	/**
	 * Reads in a line from a textfile and converts it to a room object.
	 * 
	 * @param line
	 * @throws IOException 
	 */
	private static void readRooms(String line) throws IOException {
		// Rooms need a name, description, and a list of items that are in them
		String name;
		String description="";
		LinkedList<String> items = new LinkedList<String>();
		
		/*
		 *  Comments (lines starting with #) are already skipped, so we start
		 *  with the first line, which is the item name.
		 */
		name = line;
		
		// Read the next line
		line = bufRead.readLine();
		
		// Next line will contain the description
		description = line;
		
		/*
		 * Two possibilities now - either the room has items in it or not.
		 * If not, the following line is empty or a comment.
		 */
		// buffer the current line
		String lastLine = line;
		line = bufRead.readLine();
		
		/*
		 * If the line isn't null and is neither a comment nor empty, items
		 * follow.
		 */
		if(line != null && line.length() != 0) {
			if(!line.startsWith("#")) {
				
				// While items are listed, add them to the item list.
				while(line.indexOf(";") != -1) {
					items.add(line.substring(0, line.indexOf(";")));
					line = line.substring(line.indexOf(";")+2);
				}
				// If it's the last item, we have to get it differently
				items.add(line.substring(0, line.length()));
				
			}			
		}
		/*
		 * If there are no items in the room, we are one line too far.
		 */
		else {
			line = lastLine;
		}
		
		line = bufRead.readLine();
		
		// TODO: Catch the error that occurs when a room has no items but exits.
		
		LinkedList<Exit> tempExits = new LinkedList<Exit>();
		/*
		 * If any exits follow, construct the exit object and add
		 * it to a temporary list.
		 */
		while(line != null && line.length() != 0) {
			
			// Now follows an exit, first parse the fields
			int tempX = Integer.parseInt(line.substring(0, line.indexOf(";")));
			line = line.substring(line.indexOf(";")+2);
			int tempY = Integer.parseInt(line.substring(0, line.indexOf(";")));
			line = line.substring(line.indexOf(";")+2);
			int tempWidth = Integer.parseInt(line.substring(0, line.indexOf(";")));
			line = line.substring(line.indexOf(";")+2);
			int tempHeight = Integer.parseInt(line.substring(0, line.indexOf(";")));
			line = line.substring(line.indexOf(";")+2);
			String tempDest = line.substring(0, line.length());
			
			tempExits.add(new Exit(tempX, tempY, tempWidth, tempHeight, tempDest));
			
			line = bufRead.readLine();
			
		}
		
		// create the room
		roomList.put(name, new Room(name, description));
		
		/*
		 * If the room has exits, add them.
		 */
		while(!tempExits.isEmpty()) {
						
			roomList.get(name).addExit(tempExits.getFirst());
			tempExits.removeFirst();
			
		}

		/*
		 * Put the items into the room.
		 * 
		 * Iterate through the created list of strings and add the first
		 * item in the list to the room's inventory. Afterwards, delete it
		 * from our temporary list.
		 */
		while(!items.isEmpty()) {
			roomList.get(name).getItems().put(items.getFirst(), itemList.get(items.getFirst()));
			items.removeFirst();
		}
		
		if(JLeibniz.isDebug) {
			
			outWriter.write("Created room "+roomList.get(name).getName()
					+" containing following items: "+roomList.get(name).getItems().toString()+"\n");					
			
			outWriter.write("This room has following exits: "+roomList.get(name).getExits().toString()+"\n");
		}	
	}
	
	/**
	 * Reads a line from a text file and converts the read text into an item object.
	 * 
	 * @param line
	 * @throws IOException 
	 */
	/*
	 *  TODO: Improve the parser, so it doesn't crash anymore while doing
	 *  admittedly stupid shit like trying to have empty lines or comments in one 
	 *  item declaration
	 */
	private static void readItems(String line) throws IOException {
		/*
		 *  Items need a name, description, x and y position
		 *  and possibly a target (item with which it can be combined) and a
		 *  resulting new item.
		 */
		String name;
		String description;
		int xPos;
		int yPos;
		String target = "";
		Object result = null;
		
		/*
		 *  Comments (lines starting with #) are already skipped, so we start
		 *  with the first line, which is the item name.
		 */
		name = line;
		
		// Read the next line
		line = bufRead.readLine();

		
		// Next line will contain the description.
		description = line;
		
		line = bufRead.readLine();
		
		// Now x and y coordinates
		xPos = Integer.parseInt(line.substring(0, line.indexOf(";")));
		yPos = Integer.parseInt(line.substring(line.indexOf(";")+2, line.length()));
		
		/*
		 *  Two possibilities now - either the item has combinations or not.
		 *  If not, the following line is empty or a comment.
		 */
		// buffer the current line
		String lastLine = line;
		line = bufRead.readLine();
		
		/*
		 * If the line isn't null and is neither a comment nor empty, combinations
		 * follow.
		 */
		// TODO: Crashes if the last item in the file has no combinations
		if(line.length() != 0) {
			if(!line.startsWith("#")) {			
				target = line;
				line = bufRead.readLine();
				result = itemList.get(line);
			}
			
		}
		/*
		 * If the item has no combinations, we are one line too far, so we set
		 * line back to the last line, so we don't skip one.
		 */
		else {
			line = lastLine;
		}
		
		// Now that we have everything, create the item
		itemList.put(name, new Object(name, description, xPos, yPos));
			
		// If the item has any combinations, add them.
		if(result != null) {
			itemList.get(name).addCombination(target, result);
		}
		
		if(JLeibniz.isDebug) outWriter.write("Created item "+itemList.get(name).getName()+"\n");
	}
	
	/**
	 * Main method containing the game loop
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		
		init();
		if(JLeibniz.isDebug) outWriter.write("Everything initialized.\n");
		
		// ---------------------------------------------------
		// Main Loop
		// Repeatedly update game state, render screen to a buffer and 
		// draw the buffer image. Also limit FPS.
		// ---------------------------------------------------
		long beforeTime, timeDiff, sleepTime;
		
		beforeTime = System.currentTimeMillis();
		
		while(true) {
			
			((MainFrame)mainFrame).updateGame();
			((MainFrame)mainFrame).renderScreen();
			 mainFrame.repaint();
			
			timeDiff = System.currentTimeMillis() - beforeTime;
			
			// time left in this loop
			sleepTime = period - timeDiff;
			
			if(sleepTime <= 0) {
				sleepTime = 5;
			}
			
			// Sleep a little
			try {
				// convert from ns -> ms
				Thread.sleep(sleepTime); 
			}
			catch(InterruptedException ex) {}
			
			beforeTime = System.currentTimeMillis();
			
			if(outWriter != null) outWriter.close();

		} 		
	}
	
/*public static void log(String s) {
	
	try {
		outWriter.append(calendar.get(Calendar.HOUR)+":"+calendar.get(Calendar.MINUTE)+": "+s);
		outWriter.newLine();
	} catch (IOException e) {}

}*/	
	
	
	/**
	 * Used for drawing output to the screen. Any output that should be drawn
	 * is saved into the output string, which will be put into the textbox
	 * in the next frame.
	 * 
	 * @param o	String message to be drawn on the screen.
	 */
	public static void output(String o) {
		output = o;
	}	
	
	/**
	 * Getter for the player.
	 * 
	 * @return The player.
	 */
	public static User getPlayer() {
		return player;
	}
}
